﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Krile.Kernel;
using Twinkle.Tweak;
using Krile.Kernel.Data;
using Krile.Kernel.WindowsEx;

namespace Twinkle.Common
{
    public static class AutoReceiver
    {
        public static bool TwitterDown { get; private set; }
        static Dictionary<Data.Account, Data.AccountDescriptor> DescriptorResolver = new Dictionary<Twinkle.Data.Account, Twinkle.Data.AccountDescriptor>();
        static Timer t = null;
        static bool ThreadAlive = false;
        public static void StartThread()
        {
            System.Diagnostics.Debug.WriteLine("■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■");
            System.Diagnostics.Debug.WriteLine("■ Twinkle : INITIALIZING...");
            TwitterDown = false;
            ThreadAlive = true;
            t = new Timer(Receive, null, 0, 1000);
        }

        public static void TerminateThread()
        {
            if (ThreadAlive)
            {
                ThreadAlive = false;
                t.Dispose();
            }
        }

        private static void Receive(object obj)
        {
            Receive();
        }

        public static void Receive()
        {
            try
            {
                foreach (var a in TwitterCommonOperation.EnumerateReceivableAccounts())
                {
                    TreatAccount(a);
                }
            }
            catch (Tweak.Exceptions.TwitterOAuthRequestException)
            {
                Bridge.DebugReporter.AddReport("Account credential is invalid.");
            }
        }

        private static object getDescriptorLocker = new object();
        public static Data.AccountDescriptor GetDescriptor(Data.Account a, out bool newCreated)
        {
            lock (getDescriptorLocker)
            {
                if (DescriptorResolver.ContainsKey(a))
                {
                    newCreated = false;
                    return DescriptorResolver[a];
                }
                else
                {
                    var d = new Data.AccountDescriptor();
                    DescriptorResolver.Add(a, d);
                    newCreated = true;
                    return d;
                }
            }
        }

        private static void TreatAccount(Data.Account a)
        {
            bool firstRecv;
            var d = GetDescriptor(a, out firstRecv);
            if (firstRecv)
            {
                System.Diagnostics.Debug.WriteLine(a.UserId + ":: First!");
            }
            List<Action> parallelEvents = new List<Action>(6);
            if (firstRecv)
            {
                parallelEvents.Add(() =>
                    {
                        var us = a.CredentialProvider.GetFollowersAll(null, null);
                        if (us != null)
                            foreach (var u in us)
                                if (u != null)
                                    Kernel.Storage.RegistUser(u);
                    });
                parallelEvents.Add(() =>
                    {
                        var fs = a.CredentialProvider.GetFriendsAll(null);
                        if (fs != null)
                        {
                            foreach (var u in fs)
                                if (u != null)
                                    Kernel.Storage.RegistUser(u);
                            a.followingUsers = (from u in fs select u.Id).ToArray();
                        }
                    });
            }

            if (a.UseUserStreams && !d.UserStreamsConnected && !Common.StreamManager.UserStreamConnected(a))
            {
                parallelEvents.Add(() =>
                {
                    Common.StreamManager.ConnectUserStreams(a);
                });
            }

            if (a.UseUserStreams && d.UserStreamsConnected)
            {
                // User streamsに接続されています
                // リストに関して静的受信を試みます
                d.ApiIntervalCounter++;

                if (d.ApiIntervalCounter > 60)
                {
                    d.ApiIntervalCounter = 0;
                    a.ApiRealConsumption = d.ApiPrevRemain - a.RateLimitRemain;
                    d.ApiPrevRemain = a.RateLimitRemain;
                }

                if (a.ListReceiveInterval < d.ListReceiveCount || firstRecv)
                {
                    d.ListReceiveCount = 0;
                    foreach (var l in a.ReceiveReservedLists)
                    {
                        var uid = l.Substring(1);
                        int sidx = l.IndexOf('/');
                        var lid = uid.Substring(sidx);
                        uid = uid.Substring(0, sidx);
                        System.Diagnostics.Debug.WriteLine("receive=>@" + sidx + "/" + lid);
                        parallelEvents.Add(() => RegistStatuses(a.ReceiveListTimeline(uid, lid)));
                    }
                }

            }
            else if (a.UpdatesDinamically)
            {
                //動的受信
                try
                {
                    int HTLParam = a.HomeTimelineReceivePercent;
                    if (Kernel.Plugin.TOperation.Status.RecvGlobalTimeline)
                        HTLParam = a.HomeTimelineReceivePercent * 2;

                    int DMParam = a.DMReceivePercent;
                    if (Kernel.Config.RecvFavTimeline)
                        DMParam = a.DMReceivePercent * 2;

                    int ListParam = a.ListReceivePercent;
                    if (a.ReceiveReservedLists != null)
                        ListParam *= a.ReceiveReservedLists.Length;
                    else
                        ListParam = 0;

                    double UseAPICountPerRecv = (HTLParam + a.ReplyFeedReceivePercent + DMParam + ListParam) / 100.0;
                    double ch = a.RateLimitRemain * a.ApiUseLimit / 100.0;
                    if (a.ApiRealConsumption > UseAPICountPerRecv)
                        ch /= a.ApiRealConsumption;
                    else
                        ch /= UseAPICountPerRecv;
                    double remmin = (a.RateLimitReset - DateTime.Now).TotalMinutes;
                    double minperrecv = 0;
                    if (remmin < 0 || a.RateLimitRemain == 0)
                    {
                        minperrecv = 1;
                    }
                    else
                    {
                        //残り時間 / API残り
                        minperrecv = remmin / ch;
                        if (minperrecv < 0.3)
                            minperrecv = 0.3;
                        if (minperrecv > 10)
                            minperrecv = 10;
                    }
                    d.WaitCount++;

                    if (minperrecv * 60 > d.WaitCount && !firstRecv)
                        return;
                }
                catch (Exception e)
                {
                    Bridge.DebugReporter.AddReport(e);
                    return;
                }
                d.WaitCount = 0;
                a.ApiRealConsumption = d.ApiPrevRemain - a.RateLimitRemain;
                d.ApiPrevRemain = a.RateLimitRemain;
                d.GlobalCount++;
                while (d.GlobalCount > 100)
                    d.GlobalCount -= 100;
                // freeze of global ctor's value
                var cgc = d.GlobalCount;

                if ((cgc > d.HomeReceiveCount && cgc <= d.HomeReceiveCount + 1) || firstRecv)
                {
                    d.HomeReceiveCount += a.HomeTimelineReceivePercent / 100.0;
                    while (d.HomeReceiveCount >= 100)
                        d.HomeReceiveCount -= 100;
                    parallelEvents.Add(() => RegistStatuses(a.ReceiveTimeline()));
                    if (Kernel.Plugin.TOperation.StatusControl.RecvGlobalTimeline)
                        parallelEvents.Add(() => RegistStatuses(a.ReceivePublicTimeline()));
                }
                if ((cgc > d.ReplyReceiveCount && d.GlobalCount <= cgc + 1) || firstRecv)
                {
                    d.ReplyReceiveCount += a.ReplyFeedReceivePercent / 100.0;
                    while (d.ReplyReceiveCount >= 100)
                        d.ReplyReceiveCount -= 100;
                    parallelEvents.Add(() => RegistStatuses(a.ReceiveMentions()));
                }
                if ((cgc > d.DMReceiveCount && cgc <= d.DMReceiveCount + 1) || firstRecv)
                {
                    d.DMReceiveCount += a.DMReceivePercent / 100.0;
                    while (d.DMReceiveCount >= 100)
                        d.DMReceiveCount -= 100;
                    parallelEvents.Add(() => RegistStatuses(a.ReceiveDMs()));
                }
                if (a.ReceiveReservedLists != null)
                {
                    if ((cgc > d.ListReceiveCount && cgc <= d.ListReceiveCount + 1) || firstRecv)
                    {
                        d.ListReceiveCount += a.ListReceivePercent / 100.0;
                        while (d.ListReceiveCount >= 100)
                            d.ListReceiveCount -= 100;
                        foreach (var l in a.ReceiveReservedLists)
                        {
                            var uid = l.Substring(1);
                            int sidx = l.IndexOf('/');
                            var lid = uid.Substring(sidx);
                            uid = uid.Substring(0, sidx - 1);
                            parallelEvents.Add(() => RegistStatuses(a.ReceiveListTimeline(uid, lid)));
                        }
                    }
                }
            }
            else
            {
                d.ReplyReceiveCount++;
                d.DMReceiveCount++;
                d.ApiIntervalCounter++;

                if (d.ApiIntervalCounter > 60)
                {
                    d.ApiIntervalCounter = 0;
                    a.ApiRealConsumption = d.ApiPrevRemain - a.RateLimitRemain;
                    d.ApiPrevRemain = a.RateLimitRemain;
                }
                if (a.HomeTimelineReceiveInterval < d.HomeReceiveCount || firstRecv)
                {
                    d.HomeReceiveCount = 0;

                    parallelEvents.Add(() => RegistStatuses(a.ReceiveTimeline()));
                    if (Kernel.Plugin.TOperation.StatusControl.RecvGlobalTimeline)
                        parallelEvents.Add(() => RegistStatuses(a.ReceivePublicTimeline()));
                }
                if (a.ReplyFeedReceiveInterval < d.ReplyReceiveCount || firstRecv)
                {
                    d.ReplyReceiveCount = 0;
                    parallelEvents.Add(() => RegistStatuses(a.ReceiveMentions()));
                }
                if (a.DMReceiveInterval < d.DMReceiveCount || firstRecv)
                {
                    d.DMReceiveCount = 0;

                    parallelEvents.Add(() => RegistStatuses(a.ReceiveDMs()));
                }
                if (a.ListReceiveInterval < d.ListReceiveCount || firstRecv)
                {
                    d.ListReceiveCount = 0;
                    foreach (var l in a.ReceiveReservedLists)
                    {
                        var uid = l.Substring(1);
                        int sidx = l.IndexOf('/');
                        var lid = uid.Substring(sidx);
                        uid = uid.Substring(0, sidx);
                        System.Diagnostics.Debug.WriteLine("receive=>@" + sidx + "/" + lid);
                        parallelEvents.Add(() => RegistStatuses(a.ReceiveListTimeline(uid, lid)));
                    }
                }
            }

            // parallel events executer
            if (parallelEvents.Count > 0)
            {
                    if (Environment.OSVersion.Version.Major < 6)
                    {
                        foreach (var act in parallelEvents)
                            act.Invoke();
                    }
                    else
                    {
                        foreach (var pev in parallelEvents)
                        {
                            var act = pev;
                            act.BeginInvoke((iar) =>ParallelEventEndInvoke(iar, a), act);
                        }
                    }
            }
        }

        private static void ParallelEventEndInvoke(IAsyncResult iar, Data.Account a)
        {
            try
            {
                ((Action)iar.AsyncState).EndInvoke(iar);
            }
            catch (Tweak.Exceptions.TwitterOAuthRequestException)
            {
                MessageBoxEx.Show(
                    "Account credential is invalid.",
                    "Account \"" + a.UserId + "\"'s credential information is out of date." + Environment.NewLine +
                    "Please reauthenticate it.", MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                Bridge.DebugReporter.AddReport("Account credential is invalid! :" + a.UserId);
            }
            catch (Tweak.Exceptions.TwitterXmlParseException xpe)
            {
                Bridge.DebugReporter.AddReport(xpe);
            }
            catch (NullReferenceException nre)
            {
                MessageBoxEx.Show("Twitter receiving error", "Configuration maybe collupted." + Environment.NewLine + nre.ToString() + "Twitter receiving error", MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
            }
            catch (System.IO.IOException ioe)
            {
                Bridge.DebugReporter.AddReport(ioe);
            }
        }

        static List<string> idStorage = new List<string>();
        public static void RegistStatuses(IEnumerable<TStatus> statuses)
        {
            if (statuses == null) return;
            if (Kernel.Config.GradualReceive)
            {
                foreach (var r in statuses)
                {
                    if (idStorage.Contains(r.Id))
                        continue;
                    idStorage.Add(r.Id);
                    Bridge.StatusManager.RegistStatus(new[] { r });
                }
            }
            else
            {
                var nws = from s in statuses
                          where !idStorage.Contains(s.Id)
                          select StorId(s);
                Bridge.StatusManager.RegistStatus(nws.ToArray());
            }
        }

        static Status StorId(Status s)
        {
            idStorage.Add(s.Id);
            return s;
        }
    }
}